using System;
using System.Collections.Generic;
using System.Configuration;
using System.Linq;
using System.Text;
using System.Web.Configuration;
using System.Xml;

using Microsoft.SharePoint.Administration;
using Synergy.Reflection.Extensions;

namespace Synergy.SharePoint.Configuration
{
	/// <summary>
	/// SharePoint configuration manager.
	/// </summary>
	/// <remarks>
	///		<list type="table">
	///			<listheader><term>Version</term><description>Description</description></listheader>
	///			<item>
	///				<term>1.0</term>
	///				<description>Initial version</description>
	///			</item>
	///		</list>
	/// </remarks>
	public class ConfigurationManager
	{
		#region | Initialization |

		private const string RootNodePath = "configuration";
		private const string SystemWebNodePath = RootNodePath + "/system.web";

		private readonly SPWebApplication _app;
		private readonly string _owner;


		/// <summary>
		/// Initializes a new instance of the <see cref="ConfigurationManager"/> class.
		/// </summary>
		/// <param name="app">The app.</param>
		/// <param name="modificationOwner">The modification owner.</param>
		public ConfigurationManager(SPWebApplication app, string modificationOwner)
		{
			this._app = app;
			this._owner = modificationOwner;
		}

		#endregion


		#region | Modifications |

		#region | configSections |

		private const string ConfigSectionsNode = "configSections";
		private const string ConfigSectionsNodePath = RootNodePath + "/" + ConfigSectionsNode;
		private const string ConfigSectionsNodeXml = "<" + ConfigSectionsNode + " />";
		private const string ConfigSectionsGroupNodeXmlFormat = @"<sectionGroup name=""{0}"" type=""{1}"" />";
		private const string ConfigSectionsSectionNodeXmlFormat = @"<section name=""{0}"" type=""{1}"" requirePermission=""{2}"" allowDefinition=""{3}"" />";


		/// <summary>
		/// Adds the configuration section group.
		/// </summary>
		/// <param name="group">The configuration section group.</param>
		/// <returns>The <see cref="ConfigurationManager"/> instance (to support chaining).</returns>
		/// <exception cref="ArgumentException"><paramref name="group"/> does not have either a <see cref="ConfigurationSectionBase.Name"/> or <see cref="ConfigurationSectionBase.Type"/>.</exception>
		/// <exception cref="ArgumentNullException"><paramref name="group"/> is <c>null</c>.</exception>
		public ConfigurationManager AddConfigurationSection(ConfigurationSectionGroup group)
		{
			if (group == null)
			{
				throw new ArgumentNullException("group");
			}
			AddChildNode(ConfigSectionsNode, RootNodePath, ConfigSectionsNodeXml);
			return AddConfigurationSection(group, ConfigSectionsNodePath);
		}


		/// <summary>
		/// Adds the configuration section.
		/// </summary>
		/// <param name="section">The configuration section.</param>
		/// <returns>The <see cref="ConfigurationManager"/> instance (to support chaining).</returns>
		/// <exception cref="ArgumentException"><paramref name="section"/> does not have either a <see cref="ConfigurationSectionBase.Name"/> or <see cref="ConfigurationSectionBase.Type"/>.</exception>
		/// <exception cref="ArgumentNullException"><paramref name="section"/> is <c>null</c>.</exception>
		public ConfigurationManager AddConfigurationSection(ConfigurationSection section)
		{
			if (section == null)
			{
				throw new ArgumentNullException("section");
			}
			if (String.IsNullOrEmpty(section.Name))
			{
				throw new ArgumentException("Section name is required.", "section");
			}
			if (String.IsNullOrEmpty(section.Type))
			{
				throw new ArgumentException("Section type is required.", "section");
			}
			AddChildNode(ConfigSectionsNode, RootNodePath, ConfigSectionsNodeXml);
			return AddConfigurationSection(section, ConfigSectionsNodePath);
		}


		/// <summary>
		/// Adds the configuration section group.
		/// </summary>
		/// <param name="group">The configuration section group.</param>
		/// <param name="path">The XML path of the parent node.</param>
		/// <returns>The <see cref="ConfigurationManager"/> instance (to support chaining).</returns>
		/// <exception cref="ArgumentException"><paramref name="group"/> does not have either a <see cref="ConfigurationSectionBase.Name"/> or <see cref="ConfigurationSectionBase.Type"/>.</exception>
		/// <exception cref="ArgumentNullException"><paramref name="group"/> is <c>null</c>.</exception>
		private ConfigurationManager AddConfigurationSection(ConfigurationSectionGroup group, string path)
		{
			// validate params
			if (group == null)
			{
				throw new ArgumentNullException("group");
			}
			if (String.IsNullOrEmpty(group.Name))
			{
				throw new ArgumentException("Section group name is required.", "group");
			}
			if (String.IsNullOrEmpty(group.Type))
			{
				throw new ArgumentException("Section group type is required.", "group");
			}

			// set path and add all sections and groups
			path = String.Format("{0}/sectionGroup[{1}]", path, group.Name);
			for (var i = 0; i < group.Sections.Count; i++)
			{
				AddConfigurationSection(group.Sections[i], path);
			}
			for (var i = 0; i < group.SectionGroups.Count; i++)
			{
				AddConfigurationSection(group.SectionGroups[i], path);
			}
			return this;
		}


		/// <summary>
		/// Adds the configuration section.
		/// </summary>
		/// <param name="section">The configuration section.</param>
		/// <param name="path">The XML path of the parent node.</param>
		/// <returns>
		/// The <see cref="ConfigurationManager"/> instance (to support chaining).
		/// </returns>
		/// <exception cref="ArgumentException"><paramref name="section"/> does not have either a <see cref="ConfigurationSectionBase.Name"/> or <see cref="ConfigurationSectionBase.Type"/>.</exception>
		/// <exception cref="ArgumentNullException"><paramref name="section"/> is <c>null</c>.</exception>
		private ConfigurationManager AddConfigurationSection(ConfigurationSection section, string path)
		{
			if (section == null)
			{
				throw new ArgumentNullException("section");
			}
			if (String.IsNullOrEmpty(section.Name))
			{
				throw new ArgumentException("Section name is required.", "section");
			}
			if (String.IsNullOrEmpty(section.Type))
			{
				throw new ArgumentException("Section type is required.", "section");
			}
			return AddChildNode(path, String.Format(ConfigSectionsSectionNodeXmlFormat, 
				section.Name, section.Type, section.RequirePermission, section.AllowDefinition));
		}

		#endregion


		#region | runtime / assemblyBinding / dependentAssembly |

		private const string RuntimeNode = "runtime";
		private const string RuntimeNodePath = RootNodePath + "/" + RuntimeNode;
		private const string RuntimeNodeXml = "<" + RuntimeNode + " />";

		private const string RuntimeAssemblyBindingNode = "assemblyBinding";
		private const string RuntimeAssemblyBindingNodePath = RuntimeNodePath + "/" + RuntimeAssemblyBindingNode;
		private const string RuntimeAssemblyBindingNodeXml = "<" + RuntimeAssemblyBindingNode + @" xmlns=""urn:schemas-microsoft-com:asm.v1"" />";

		private const string RuntimeDependentAssemblyNode = "dependentAssembly";
		private const string RuntimeDependentAssemblyNodePath = CodeDomCompilersNodePath + "/" + RuntimeDependentAssemblyNode;
		private const string RuntimeDependentAssemblyIdentityNode = "assemblyIdentity";
		private const string RuntimeDependentAssemblyNodeNameFormat = RuntimeDependentAssemblyNodePath + "[@" + RuntimeDependentAssemblyIdentityNode + @"='{0}']";
		private const string RuntimeDependentAssemblyNodeXmlFormat = "<" + RuntimeDependentAssemblyNode + "><" + RuntimeDependentAssemblyIdentityNode + @" name=""{0}"" publicKeyToken=""{1}""/><bindingRedirect oldVersion=""{2}"" newVersion=""{3}"" /></" + RuntimeDependentAssemblyNode + ">";


		/// <summary>
		/// Adds a depenency for a runtime assembly binding.
		/// </summary>
		/// <param name="assemblyType">Type from the assembly to be registered.</param>
		/// <param name="oldVersion">The old version range of the assembly.</param>
		/// <returns>
		/// The <see cref="ConfigurationManager"/> instance (to support chaining).
		/// </returns>
		/// <exception cref="ArgumentNullException"><paramref name="oldVersion"/> is <c>null</c> or empty or <paramref name="assemblyType"/> is <c>null</c>.</exception>
		public ConfigurationManager AddDependentAssembly(Type assemblyType, string oldVersion)
		{
			if (assemblyType == null)
			{
				throw new ArgumentNullException("assemblyType");
			}
			var assembly = assemblyType.Assembly.GetName();
			return AddDependentAssembly(assembly.Name, assembly.GetPublicKeyTokenString(), oldVersion, assembly.Version.ToString());
		}


		/// <summary>
		/// Adds a depenency for a runtime assembly binding.
		/// </summary>
		/// <param name="assemblyName">Name of the assembly.</param>
		/// <param name="assemblyPublicKeyToken">The assembly public key token.</param>
		/// <param name="oldVersion">The old version range of the assembly.</param>
		/// <param name="newVersion">The new version to redirect to.</param>
		/// <returns>The <see cref="ConfigurationManager"/> instance (to support chaining).</returns>
		/// <exception cref="ArgumentNullException"><paramref name="assemblyName"/>, <paramref name="assemblyPublicKeyToken"/>, <paramref name="oldVersion"/>, or <paramref name="newVersion"/> is <c>null</c> or empty.</exception>
		public ConfigurationManager AddDependentAssembly(string assemblyName, string assemblyPublicKeyToken, string oldVersion, string newVersion)
		{
			// validate params
			if (String.IsNullOrEmpty(assemblyName))
			{
				throw new ArgumentNullException("assemblyName");
			}
			if (String.IsNullOrEmpty(assemblyPublicKeyToken))
			{
				throw new ArgumentNullException("assemblyPublicKeyToken");
			}
			if (String.IsNullOrEmpty(oldVersion))
			{
				throw new ArgumentNullException("oldVersion");
			}
			if (String.IsNullOrEmpty(newVersion))
			{
				throw new ArgumentNullException("newVersion");
			}

			// add nodes
			AddChildNode(RuntimeNode, RootNodePath, RuntimeNodeXml);
			AddChildNode(RuntimeAssemblyBindingNodePath, RuntimeNodePath, RuntimeAssemblyBindingNodeXml);
			return AddChildNode(String.Format(RuntimeDependentAssemblyNodeNameFormat, assemblyName), RuntimeDependentAssemblyNodePath, 
				String.Format(RuntimeDependentAssemblyNodeXmlFormat, assemblyName, assemblyPublicKeyToken, oldVersion, newVersion));
		}

		#endregion


		#region | system.codedom / compilers / compiler |

		private const string CodeDomNode = "system.codedom";
		private const string CodeDomNodePath = RootNodePath + "/" + CodeDomNode;
		private const string CodeDomNodeXml = "<" + CodeDomNode + " />";
		
		private const string CodeDomCompilersNode = "compilers";
		private const string CodeDomCompilersNodePath = CodeDomNodePath + "/" + CodeDomCompilersNode;
		private const string CodeDomCompilersNodeXml = "<" + CodeDomCompilersNode + " />";
		
		private const string CodeDomCompilerNode = "compiler";
		private const string CodeDomCompilerNodePath = CodeDomCompilersNodePath + "/" + CodeDomCompilerNode;
		private const string CodeDomCompilerNodeXmlFormat = "<" + CodeDomCompilerNode + @" language=""{0}"" extension=""{1}"" warningLevel=""{2}"" type=""{3}"" />";
		private const string CodeDomCompilerOptionNodeXmlFormat = @"<providerOption name=""{0}"" value=""{1}"" />";


		/// <summary>
		/// Adds a code dom compiler.
		/// </summary>
		/// <param name="language">The language.</param>
		/// <param name="extension">The file extension.</param>
		/// <param name="warningLevel">The warning level.</param>
		/// <param name="provider">The type of the provider.</param>
		/// <param name="providerOptions">The provider options.</param>
		/// <returns>The <see cref="ConfigurationManager"/> instance (to support chaining).</returns>
		/// <exception cref="ArgumentNullException"><paramref name="language"/> or <paramref name="extension"/> is <c>null</c> or empty or <paramref name="provider"/> is <c>null</c>.</exception>
		public ConfigurationManager AddCodeDomCompiler(string language, string extension, int warningLevel, Type provider, Dictionary<string, object> providerOptions)
		{
			// validate params
			if (String.IsNullOrEmpty(language))
			{
				throw new ArgumentNullException("language");
			}
			if (String.IsNullOrEmpty(extension))
			{
				throw new ArgumentNullException("extension");
			}
			if (provider == null)
			{
				throw new ArgumentNullException("provider");
			}

			// add nodes
			AddChildNode(CodeDomNode, RootNodePath, CodeDomNodeXml);
			AddChildNode(CodeDomCompilersNodePath, CodeDomNodePath, CodeDomCompilersNodeXml);
			AddChildNode(CodeDomNodePath, String.Format(CodeDomCompilerNodeXmlFormat, language, extension, warningLevel, provider.AssemblyQualifiedName));
			
			// add provider options, if any
			if (providerOptions == null)
			{
				return this;
			}
			foreach (var pair in providerOptions)
			{
				AddChildNode(CodeDomCompilerNodePath + "[@language="+language+"]", 
					String.Format(CodeDomCompilerOptionNodeXmlFormat, pair.Key, pair.Value));
			}
			return this;
		}

		#endregion


		#region | system.web / compilation / assemblies |

		private const string CompilationAssembliesNodePath = SystemWebNodePath + "/compilation/assemblies";
		private const string CompilationAssembliesAddNodeFormat = @"<add assembly=""{0}"" />";


		/// <summary>
		/// Adds the compilation assembly.
		/// </summary>
		/// <param name="assemblyType">Class defined within the assembly to be registered.</param>
		/// <returns>The <see cref="ConfigurationManager"/> instance (to support chaining).</returns>
		public ConfigurationManager AddCompilationAssembly(Type assemblyType)
		{
			return AddCompilationAssembly(assemblyType.Assembly.FullName);
		}


		/// <summary>
		/// Adds the compilation assembly.
		/// </summary>
		/// <param name="assemblyFullName">Full name of the assembly.</param>
		/// <returns>The <see cref="ConfigurationManager"/> instance (to support chaining).</returns>
		public ConfigurationManager AddCompilationAssembly(string assemblyFullName)
		{
			return AddChildNode(CompilationAssembliesNodePath, String.Format(CompilationAssembliesAddNodeFormat, assemblyFullName));
		}

		#endregion


		#region | system.web / httpHandlers |

		private const string HttpHandlersNode = "httpHandlers";
		private const string HttpHandlersNodePath = SystemWebNodePath + "/" + HttpHandlersNode;
		private const string HttpHandlersNodeXml = "<" + HttpHandlersNode + " />";
		private const string HttpHandlersAddNodeFormat = @"<add verb=""{0}"" path=""{1}"" type=""{2}"" validate=""{3}"" />";
		private const bool HttpHandlersAddNodeValidationDefault = false;
		private const string HttpHandlersRemoveNodeFormat = @"<remove verb=""{0}"" path=""{1}"" />";


		/// <summary>
		/// Adds a new HTTP handler to the ASP.NET pipeline.
		/// </summary>
		/// <param name="verb">The HTTP verb(s) to support.</param>
		/// <param name="path">The path to accept.</param>
		/// <param name="handlerType">Type of the handler.</param>
		/// <returns>The <see cref="ConfigurationManager"/> instance (to support chaining).</returns>
		/// <exception cref="ArgumentNullException"><paramref name="verb"/> or <paramref name="path"/> is <c>null</c> or empty or <paramref name="handlerType"/> is <c>null</c>.</exception>
		public ConfigurationManager AddHttpHandler(string verb, string path, Type handlerType)
		{
			return AddHttpHandler(verb, path, handlerType, HttpHandlersAddNodeValidationDefault);
		}


		/// <summary>
		/// Adds a new HTTP handler to the ASP.NET pipeline.
		/// </summary>
		/// <param name="verb">The HTTP verb(s) to support.</param>
		/// <param name="path">The path to accept.</param>
		/// <param name="handlerType">Type of the handler.</param>
		/// <param name="validate">Specifies whether the path should be validated.</param>
		/// <returns>The <see cref="ConfigurationManager"/> instance (to support chaining).</returns>
		/// <exception cref="ArgumentNullException"><paramref name="verb"/> or <paramref name="path"/> is <c>null</c> or empty or <paramref name="handlerType"/> is <c>null</c>.</exception>
		public ConfigurationManager AddHttpHandler(string verb, string path, Type handlerType, bool validate)
		{
			if (handlerType == null)
			{
				throw new ArgumentNullException("handlerType");
			}
			return AddHttpHandler(verb, path, handlerType.AssemblyQualifiedName, validate);
		}


		/// <summary>
		/// Adds a new HTTP handler to the ASP.NET pipeline.
		/// </summary>
		/// <param name="verb">The HTTP verb(s) to support.</param>
		/// <param name="path">The path to accept.</param>
		/// <param name="assemblyQualifiedHandler">The assembly-qualified HTTP handler type.</param>
		/// <returns>The <see cref="ConfigurationManager"/> instance (to support chaining).</returns>
		/// <exception cref="ArgumentNullException"><paramref name="verb"/>, <paramref name="path"/>, or <paramref name="assemblyQualifiedHandler"/> is <c>null</c> or empty.</exception>
		public ConfigurationManager AddHttpHandler(string verb, string path, string assemblyQualifiedHandler)
		{
			return AddHttpHandler(verb, path, assemblyQualifiedHandler, HttpHandlersAddNodeValidationDefault);
		}


		/// <summary>
		/// Adds a new HTTP handler to the ASP.NET pipeline.
		/// </summary>
		/// <param name="verb">The HTTP verb(s) to support.</param>
		/// <param name="path">The path to accept.</param>
		/// <param name="assemblyQualifiedHandler">The assembly-qualified HTTP handler type.</param>
		/// <param name="validate">Specifies whether the path should be validated.</param>
		/// <returns>The <see cref="ConfigurationManager"/> instance (to support chaining).</returns>
		/// <exception cref="ArgumentNullException"><paramref name="verb"/>, <paramref name="path"/>, or <paramref name="assemblyQualifiedHandler"/> is <c>null</c> or empty.</exception>
		public ConfigurationManager AddHttpHandler(string verb, string path, string assemblyQualifiedHandler, bool validate)
		{
			if (String.IsNullOrEmpty(verb))
			{
				throw new ArgumentNullException("verb");
			}
			if (String.IsNullOrEmpty(path))
			{
				throw new ArgumentNullException("path");
			}
			if (String.IsNullOrEmpty(assemblyQualifiedHandler))
			{
				throw new ArgumentNullException("assemblyQualifiedHandler");
			}
			AddChildNode(HttpHandlersNode, SystemWebNodePath, HttpHandlersNodeXml);
			return AddChildNode(HttpHandlersNodePath, String.Format(HttpHandlersAddNodeFormat, verb, path, assemblyQualifiedHandler, validate));
		}


		/// <summary>
		/// Removes an HTTP handler configured for the specified <paramref name="verb"/> and <paramref name="path"/>.
		/// </summary>
		/// <param name="verb">The verb.</param>
		/// <param name="path">The path.</param>
		/// <returns>The <see cref="ConfigurationManager"/> instance (to support chaining).</returns>
		/// <exception cref="ArgumentNullException"><paramref name="verb"/> or <paramref name="path"/> is <c>null</c> or empty.</exception>
		public ConfigurationManager RemoveHttpHandler(string verb, string path)
		{
			if (String.IsNullOrEmpty(verb))
			{
				throw new ArgumentNullException("verb");
			}
			if (String.IsNullOrEmpty(path))
			{
				throw new ArgumentNullException("path");
			}
			AddChildNode(HttpHandlersNode, SystemWebNodePath, HttpHandlersNodeXml);
			return AddChildNode(HttpHandlersNodePath, String.Format(HttpHandlersRemoveNodeFormat, verb, path));
		}

		#endregion


		#region | system.web / httpModules |

		private const string HttpModulesNode = "httpModules";
		private const string HttpModulesNodePath = SystemWebNodePath + "/" + HttpModulesNode;
		private const string HttpModulesNodeXml = "<" + HttpModulesNode + " />";
		private const string HttpModulesAddNodeFormat = @"<add name=""{0}"" type=""{1}"" />";
		private const string HttpModulesRemoveNodeFormat = @"<remove name=""{0}"" />";


		/// <summary>
		/// Adds a new HTTP module to the ASP.NET pipeline.
		/// </summary>
		/// <param name="moduleType">Type of the HTTP module.</param>
		/// <returns>The <see cref="ConfigurationManager"/> instance (to support chaining).</returns>
		/// <exception cref="ArgumentNullException"><paramref name="moduleType"/> is <c>null</c>.</exception>
		public ConfigurationManager AddHttpModule(Type moduleType)
		{
			if (moduleType == null)
			{
				throw new ArgumentNullException("moduleType");
			}
			return AddHttpModule(moduleType.Name, moduleType);
		}


		/// <summary>
		/// Adds a new HTTP module to the ASP.NET pipeline.
		/// </summary>
		/// <param name="name">The name to register the HTTP module as.</param>
		/// <param name="moduleType">Type of the HTTP module.</param>
		/// <returns>The <see cref="ConfigurationManager"/> instance (to support chaining).</returns>
		/// <exception cref="ArgumentNullException"><paramref name="name"/> is <c>null</c> or empty or <paramref name="moduleType"/> is <c>null</c>.</exception>
		public ConfigurationManager AddHttpModule(string name, Type moduleType)
		{
			if (moduleType == null)
			{
				throw new ArgumentNullException("moduleType");
			}
			return AddHttpModule(name, moduleType.AssemblyQualifiedName);
		}


		/// <summary>
		/// Adds a new HTTP module to the ASP.NET pipeline.
		/// </summary>
		/// <param name="name">The name to register the HTTP module as.</param>
		/// <param name="assemblyQualifiedModule">Type of the HTTP module.</param>
		/// <returns>The <see cref="ConfigurationManager"/> instance (to support chaining).</returns>
		/// <exception cref="ArgumentNullException"><paramref name="name"/> or <paramref name="assemblyQualifiedModule"/> is <c>null</c> or empty.</exception>
		public ConfigurationManager AddHttpModule(string name, string assemblyQualifiedModule)
		{
			if (String.IsNullOrEmpty(name))
			{
				throw new ArgumentNullException("name");
			}
			if (String.IsNullOrEmpty(assemblyQualifiedModule))
			{
				throw new ArgumentNullException("assemblyQualifiedModule");
			}
			AddChildNode(HttpModulesNode, SystemWebNodePath, HttpModulesNodeXml);
			return AddChildNode(HttpModulesNodePath, String.Format(HttpModulesAddNodeFormat, name, assemblyQualifiedModule));
		}


		/// <summary>
		/// Removes an HTTP module from the ASP.NET pipeline.
		/// </summary>
		/// <param name="name">The name of the registered HTTP module to remove.</param>
		/// <returns>The <see cref="ConfigurationManager"/> instance (to support chaining).</returns>
		/// <exception cref="ArgumentNullException"><paramref name="name"/> is <c>null</c> or empty.</exception>
		public ConfigurationManager RemoveHttpModule(string name)
		{
			if (String.IsNullOrEmpty(name))
			{
				throw new ArgumentNullException("name");
			}
			AddChildNode(HttpModulesNode, SystemWebNodePath, HttpModulesNodeXml);
			return AddChildNode(HttpModulesNodePath, String.Format(HttpModulesRemoveNodeFormat, name));
		}

		#endregion


		#region | system.web / pages / controls |

		private const string PagesNode = "pages";
		private const string PagesNodePath = SystemWebNodePath + "/" + PagesNode;
		private const string PagesNodeXml = "<" + PagesNode + " />";

		private const string PagesControlsNode = "controls";
		private const string PagesControlsNodePath = PagesNodePath + "/" + PagesControlsNode;
		private const string PagesControlsNodeXml = "<" + PagesControlsNode + " />";
		private const string PagesControlsAddNamespaceNodeFormat = @"<add tagPrefix=""{0}"" namespace=""{1}"" assembly=""{2}"" />";
		private const string PagesControlsAddControlNodeFormat = @"<add tagPrefix=""{0}"" tagName=""{1}"" src=""{2}"" />";


		/// <summary>
		/// Registers the server control namespace for use on all pages and user controls.
		/// </summary>
		/// <param name="prefix">The tag prefix.</param>
		/// <param name="namespaceType">Class defined within the namespace and assembly to be registered.</param>
		/// <returns>The <see cref="ConfigurationManager"/> instance (to support chaining).</returns>
		/// <exception cref="ArgumentNullException"><paramref name="prefix"/> is <c>null</c> or empty or <paramref name="namespaceType"/> is <c>null</c>.</exception>
		public ConfigurationManager AddControlNamespace(string prefix, Type namespaceType)
		{
			if (namespaceType == null)
			{
				throw new ArgumentNullException("namespaceType");
			}
			return AddControlNamespace(prefix, namespaceType.Namespace, namespaceType.Assembly.FullName);
		}


		/// <summary>
		/// Registers the server control namespace for use on all pages and user controls.
		/// </summary>
		/// <param name="tagPrefix">The tag prefix.</param>
		/// <param name="namespaceName">Name of the namespace.</param>
		/// <param name="assemblyFullName">Full name of the assembly.</param>
		/// <returns>The <see cref="ConfigurationManager"/> instance (to support chaining).</returns>
		/// <exception cref="ArgumentNullException"><paramref name="tagPrefix"/>, <paramref name="namespaceName"/>, or <paramref name="assemblyFullName"/> is <c>null</c> or empty.</exception>
		public ConfigurationManager AddControlNamespace(string tagPrefix, string namespaceName, string assemblyFullName)
		{
			if (String.IsNullOrEmpty(tagPrefix))
			{
				throw new ArgumentNullException("tagPrefix");
			}
			if (String.IsNullOrEmpty(namespaceName))
			{
				throw new ArgumentNullException("namespaceName");
			}
			if (String.IsNullOrEmpty(assemblyFullName))
			{
				throw new ArgumentNullException("assemblyFullName");
			}
			AddChildNode(PagesNode, SystemWebNodePath, PagesNodeXml);
			AddChildNode(PagesControlsNode, PagesNodePath, PagesControlsNodeXml);
			return AddChildNode(PagesControlsNodePath, String.Format(PagesControlsAddNamespaceNodeFormat, tagPrefix, namespaceName, assemblyFullName));
		}


		/// <summary>
		/// Registers the user control for use on all pages and user controls.
		/// </summary>
		/// <param name="tagPrefix">The tag prefix.</param>
		/// <param name="tagName">The tag name.</param>
		/// <param name="controlPath">The control path.</param>
		/// <returns>The <see cref="ConfigurationManager"/> instance (to support chaining).</returns>
		/// <exception cref="ArgumentNullException"><paramref name="tagPrefix"/>, <paramref name="tagName"/>, or <paramref name="controlPath"/> is <c>null</c> or empty.</exception>
		public ConfigurationManager AddControl(string tagPrefix, string tagName, string controlPath)
		{
			if (String.IsNullOrEmpty(tagPrefix))
			{
				throw new ArgumentNullException("tagPrefix");
			}
			if (String.IsNullOrEmpty(tagName))
			{
				throw new ArgumentNullException("tagName");
			}
			if (String.IsNullOrEmpty(controlPath))
			{
				throw new ArgumentNullException("controlPath");
			}
			AddChildNode(PagesNode, SystemWebNodePath, PagesNodeXml);
			AddChildNode(PagesControlsNode, PagesNodePath, PagesControlsNodeXml);
			return AddChildNode(PagesControlsNodePath, String.Format(PagesControlsAddControlNodeFormat, tagPrefix, tagName, controlPath));
		}

		#endregion


		#region | system.web / xhtmlConformance |

		private const string XhtmlConformanceNode = "xhtmlConformance";
		private const string XhtmlConformanceNodePath = SystemWebNodePath + "/" + XhtmlConformanceNode;
		private const string XhtmlConformanceNodeXml = "<" + XhtmlConformanceNode + " />";
		private const string XhtmlConformanceModeAttribute = "mode";


		/// <summary>
		/// Sets the XHTML conformance.
		/// </summary>
		/// <param name="mode">The XHTML conformance mode.</param>
		/// <returns>The <see cref="ConfigurationManager"/> instance (to support chaining).</returns>
		public ConfigurationManager SetXhtmlConformance(XhtmlConformanceMode mode)
		{
			AddChildNode(XhtmlConformanceNode, SystemWebNodePath, XhtmlConformanceNodeXml);
			return SetAttribute(XhtmlConformanceNodePath, XhtmlConformanceModeAttribute, mode.ToString());
		}

		#endregion


		#region | system.webServer |

		private const string SystemWebServerNode = "system.webServer";
		private const string SystemWebServerNodePath = RootNodePath + "/" + SystemWebServerNode;
		private const string SystemWebServerNodeXml = "<" + SystemWebServerNode + " />";


		/// <summary>
		/// Adds the root web server configuration node.
		/// </summary>
		/// <returns>The <see cref="ConfigurationManager"/> instance (to support chaining).</returns>
		public ConfigurationManager AddWebServer()
		{
			return AddChildNode(SystemWebServerNodePath, SystemWebNodePath, SystemWebServerNodeXml);
		}

		#endregion


		#region | system.webServer / handlers |

		private const string WebServerHandlersNode = "handlers";
		private const string WebServerHandlersNodePath = SystemWebServerNodePath + "/" + WebServerHandlersNode;
		private const string WebServerHandlersNodeXml = "<" + WebServerHandlersNode + " />";
		private const string WebServerHandlersAddNodeFormat = @"<add name=""{0}"" verb=""{1}"" path=""{2}"" preCondition=""{3}"" type=""{4}"" />";
		private const string WebServerHandlersRemoveNodeFormat = @"<remove name=""{0}"" />";


		/// <summary>
		/// Adds a new web server handler to the pipeline.
		/// </summary>
		/// <param name="verb">The web server verb(s) to support.</param>
		/// <param name="path">The path to accept.</param>
		/// <param name="preCondition">The pre condition.</param>
		/// <param name="handlerType">Type of the handler.</param>
		/// <returns>The <see cref="ConfigurationManager"/> instance (to support chaining).</returns>
		/// <exception cref="ArgumentNullException"><paramref name="verb"/>, <paramref name="path"/>, or <paramref name="preCondition"/> is <c>null</c> or empty or <paramref name="handlerType"/> is <c>null</c>.</exception>
		public ConfigurationManager AddWebServerHandler(string verb, string path, string preCondition, Type handlerType)
		{
			return AddWebServerHandler(null, verb, path, preCondition, handlerType.AssemblyQualifiedName);
		}


		/// <summary>
		/// Adds a new web server handler to the pipeline.
		/// </summary>
		/// <param name="name">The name of the web server handler.</param>
		/// <param name="verb">The web server verb(s) to support.</param>
		/// <param name="path">The path to accept.</param>
		/// <param name="preCondition">The pre condition.</param>
		/// <param name="handlerType">Type of the handler.</param>
		/// <returns>The <see cref="ConfigurationManager"/> instance (to support chaining).</returns>
		/// <exception cref="ArgumentNullException"><paramref name="verb"/>, <paramref name="path"/>, or <paramref name="preCondition"/> is <c>null</c> or empty or <paramref name="handlerType"/> is <c>null</c>.</exception>
		public ConfigurationManager AddWebServerHandler(string name, string verb, string path, string preCondition, Type handlerType)
		{
			if (handlerType == null)
			{
				throw new ArgumentNullException("handlerType");
			}
			if (String.IsNullOrEmpty(name))
			{
				name = handlerType.Name;
			}
			return AddWebServerHandler(name, verb, path, preCondition, handlerType.AssemblyQualifiedName);
		}


		/// <summary>
		/// Adds a new web server handler to the pipeline.
		/// </summary>
		/// <param name="name">The name of the web server handler.</param>
		/// <param name="verb">The web server verb(s) to support.</param>
		/// <param name="path">The path to accept.</param>
		/// <param name="preCondition">The pre condition.</param>
		/// <param name="assemblyQualifiedHandler">The assembly-qualified web server handler type.</param>
		/// <returns>The <see cref="ConfigurationManager"/> instance (to support chaining).</returns>
		/// <exception cref="ArgumentNullException"><paramref name="name"/>, <paramref name="verb"/>, <paramref name="path"/>, <paramref name="preCondition"/>, or <paramref name="assemblyQualifiedHandler"/> is <c>null</c> or empty.</exception>
		public ConfigurationManager AddWebServerHandler(string name, string verb, string path, string preCondition, string assemblyQualifiedHandler)
		{
			if (String.IsNullOrEmpty(name))
			{
				throw new ArgumentNullException("name");
			}
			if (String.IsNullOrEmpty(verb))
			{
				throw new ArgumentNullException("verb");
			}
			if (String.IsNullOrEmpty(path))
			{
				throw new ArgumentNullException("path");
			}
			if (String.IsNullOrEmpty(preCondition))
			{
				throw new ArgumentNullException("preCondition");
			}
			if (String.IsNullOrEmpty(assemblyQualifiedHandler))
			{
				throw new ArgumentNullException("assemblyQualifiedHandler");
			}
			AddWebServer();
			AddChildNode(WebServerHandlersNode, SystemWebServerNodePath, WebServerHandlersNodeXml);
			return AddChildNode(WebServerHandlersNodePath, String.Format(WebServerHandlersAddNodeFormat, name, verb, path, preCondition, assemblyQualifiedHandler));
		}


		/// <summary>
		/// Removes a web server handler configured for the specified <paramref name="name"/>.
		/// </summary>
		/// <param name="name">The name.</param>
		/// <returns>The <see cref="ConfigurationManager"/> instance (to support chaining).</returns>
		/// <exception cref="ArgumentNullException"><paramref name="name"/> is <c>null</c> or empty.</exception>
		public ConfigurationManager RemoveWebServerHandler(string name)
		{
			if (String.IsNullOrEmpty(name))
			{
				throw new ArgumentNullException("name");
			}
			AddWebServer();
			AddChildNode(WebServerHandlersNode, SystemWebServerNodePath, WebServerHandlersNodeXml);
			return AddChildNode(WebServerHandlersNodePath, String.Format(WebServerHandlersRemoveNodeFormat, name));
		}

		#endregion


		#region | system.webServer / modules |

		private const string WebServerModulesNode = "modules";
		private const string WebServerModulesNodePath = SystemWebServerNodePath + "/" + WebServerModulesNode;
		private const string WebServerModulesNodeXml = "<" + WebServerModulesNode + " />";
		private const string WebServerModulesAddNodeFormat = @"<add name=""{0}"" preCondition=""{1}"" type=""{2}"" />";
		private const string WebServerModulesRemoveNodeFormat = @"<remove name=""{0}"" />";


		/// <summary>
		/// Adds a new web server module to the pipeline.
		/// </summary>
		/// <param name="preCondition">The pre condition.</param>
		/// <param name="moduleType">Type of the web server module.</param>
		/// <returns>The <see cref="ConfigurationManager"/> instance (to support chaining).</returns>
		/// <exception cref="ArgumentNullException"><paramref name="preCondition"/> is <c>null</c> or empty or <paramref name="moduleType"/> is <c>null</c>.</exception>
		public ConfigurationManager AddWebServerModule(string preCondition, Type moduleType)
		{
			return AddWebServerModule(null, preCondition, moduleType);
		}


		/// <summary>
		/// Adds a new web server module to the pipeline.
		/// </summary>
		/// <param name="name">The name of the module.</param>
		/// <param name="preCondition">The pre condition.</param>
		/// <param name="moduleType">Type of the web server module.</param>
		/// <returns>The <see cref="ConfigurationManager"/> instance (to support chaining).</returns>
		/// <exception cref="ArgumentNullException"><paramref name="preCondition"/> is <c>null</c> or empty or <paramref name="moduleType"/> is <c>null</c>.</exception>
		public ConfigurationManager AddWebServerModule(string name, string preCondition, Type moduleType)
		{
			if (moduleType == null)
			{
				throw new ArgumentNullException("moduleType");
			}
			if (String.IsNullOrEmpty(name))
			{
				name = moduleType.Name;
			}
			return AddWebServerModule(name, preCondition, moduleType.AssemblyQualifiedName);
		}


		/// <summary>
		/// Adds a new web server module to the pipeline.
		/// </summary>
		/// <param name="name">The name of the module.</param>
		/// <param name="preCondition">The pre condition.</param>
		/// <param name="assemblyQualifiedModule">Assembly-qualified type of the web server module.</param>
		/// <returns>The <see cref="ConfigurationManager"/> instance (to support chaining).</returns>
		/// <exception cref="ArgumentNullException"><paramref name="name"/>, <paramref name="preCondition"/>, or <paramref name="assemblyQualifiedModule"/> is <c>null</c> or empty.</exception>
		public ConfigurationManager AddWebServerModule(string name, string preCondition, string assemblyQualifiedModule)
		{
			if (String.IsNullOrEmpty(name))
			{
				throw new ArgumentNullException("name");
			}
			if (String.IsNullOrEmpty(preCondition))
			{
				throw new ArgumentNullException("preCondition");
			}
			if (String.IsNullOrEmpty(assemblyQualifiedModule))
			{
				throw new ArgumentNullException("assemblyQualifiedModule");
			}
			AddWebServer();
			AddChildNode(WebServerModulesNode, SystemWebServerNodePath, WebServerModulesNodeXml);
			return AddChildNode(WebServerModulesNodePath, String.Format(WebServerModulesAddNodeFormat, name, preCondition, assemblyQualifiedModule));
		}


		/// <summary>
		/// Removes a web server module from the pipeline.
		/// </summary>
		/// <param name="name">The name of the registered web server module to remove.</param>
		/// <returns>The <see cref="ConfigurationManager"/> instance (to support chaining).</returns>
		/// <exception cref="ArgumentNullException"><paramref name="name"/> is <c>null</c> or empty.</exception>
		public ConfigurationManager RemoveWebServerModule(string name)
		{
			if (String.IsNullOrEmpty(name))
			{
				throw new ArgumentNullException("name");
			}
			AddWebServer();
			AddChildNode(WebServerModulesNode, SystemWebServerNodePath, WebServerModulesNodeXml);
			return AddChildNode(WebServerModulesNodePath, String.Format(WebServerModulesRemoveNodeFormat, name));
		}

		#endregion


		#region | system.webServer / validation |

		private const string WebServerValidationNode = "validation";
		private const string WebServerValidationNodePath = SystemWebServerNodePath + "/" + WebServerValidationNode;
		private const string WebServerValidationNodeXml = "<" + WebServerValidationNode + " />";
		private const string WebServerValidationValidateIntegratedModeAttribute = "validateIntegratedModeConfiguration";


		/// <summary>
		/// Sets a flag to validate web server integrated mode.
		/// </summary>
		/// <param name="enabled">Specifies whether integrated mode validation should be enabled or not.</param>
		/// <returns>The <see cref="ConfigurationManager"/> instance (to support chaining).</returns>
		public ConfigurationManager ValidateIntegratedMode(bool enabled)
		{
			AddWebServer();
			AddChildNode(WebServerValidationNodePath, WebServerValidationNode, WebServerValidationNodeXml);
			return SetAttribute(WebServerValidationNodePath, WebServerValidationValidateIntegratedModeAttribute, enabled);
		}

		#endregion

		#endregion


		#region | Public Methods |

		/// <summary>
		/// Determines whether any web.config modifications exist for the specified <see name="_owner"/>.
		/// </summary>
		/// <returns><c>true</c> if a web.config modification already exists; otherwise, <c>false</c>.</returns>
		public bool ContainsAny()
		{
			/* .NET 3.0
			foreach (var mod in this._app.WebConfigModifications)
			{
				if (String.Compare(mod.Owner, this._owner, StringComparison.CurrentCultureIgnoreCase) == 0)
				{
					return true;
				}
			}
			return false;
			*/
			return (from m in this._app.WebConfigModifications.AsEnumerable() where m.Owner == this._owner select m).Any();
		}


		/// <summary>
		/// Removes all web.config modifications with the specified <see name="_owner"/>.
		/// </summary>
		/// <returns></returns>
		public ConfigurationManager RemoveAll()
		{
			var mods = this._app.WebConfigModifications;
			for (var i = mods.Count - 1; i >= 0; i--)
			{
				var mod = mods[i];
				if (String.Compare(mod.Owner, this._owner, StringComparison.CurrentCultureIgnoreCase) == 0)
				{
					mods.RemoveAt(i);
				}
			}
			return this;
		}


		/// <summary>
		/// Updates the web application changes and applies web.config modifications.
		/// </summary>
		/// <returns></returns>
		public ConfigurationManager Update()
		{
			this._app.Update();
			this._app.Farm.Services.GetValue<SPWebService>().ApplyWebConfigModifications();
			return this;
		}

		#endregion


		#region | Private Web.config Wrappers |

		#region | Add Child Node |

		/// <summary>
		/// Adds a new child node web.config modification.
		/// </summary>
		/// <param name="path">The xpath expression indicating the desired configuration node.</param>
		/// <param name="value">The XML value to change.</param>
		/// <returns>The <see cref="ConfigurationManager"/> instance (to support chaining).</returns>
		private ConfigurationManager AddChildNode(string path, string value)
		{
			return AddChildNode(null, path, value);
		}


		/// <summary>
		/// Adds a new child node web.config modification.
		/// </summary>
		/// <param name="name">The modification name.</param>
		/// <param name="path">The xpath expression indicating the desired configuration node.</param>
		/// <param name="value">The XML value to change.</param>
		/// <returns>The <see cref="ConfigurationManager"/> instance (to support chaining).</returns>
		private ConfigurationManager AddChildNode(string name, string path, string value)
		{
			// get valid name
			name = String.IsNullOrEmpty(name) ? GenerateModificationName(value) : name;

			// add web config mod to app
			var mod = new SPWebConfigModification
			{
				Name = name,
				Path = path,
				Type = SPWebConfigModification.SPWebConfigModificationType.EnsureChildNode,
				Owner = this._owner,
				Value = value,
				Sequence = 0
			};
			this._app.WebConfigModifications.Add(mod);

			// return config mgr to support chaining
			return this;
		}

		#endregion


		#region | Set Attribute |

		/// <summary>
		/// Sets the specified <paramref name="attribute"/> a new web.config modification.
		/// </summary>
		/// <param name="nodePath">The xpath expression indicating the desired configuration node to set the <paramref name="attribute"/> for.</param>
		/// <param name="attribute">The name of the configuration node attribute to set.</param>
		/// <param name="value">The attribute value to set.</param>
		/// <returns>The <see cref="ConfigurationManager"/> instance (to support chaining).</returns>
		private ConfigurationManager SetAttribute(string nodePath, string attribute, bool value)
		{
			return SetAttribute(nodePath, attribute, value.ToString());
		}


		/// <summary>
		/// Sets the specified <paramref name="attribute"/> a new web.config modification.
		/// </summary>
		/// <param name="nodePath">The xpath expression indicating the desired configuration node to set the <paramref name="attribute"/> for.</param>
		/// <param name="attribute">The name of the configuration node attribute to set.</param>
		/// <param name="value">The attribute value to set.</param>
		/// <returns>The <see cref="ConfigurationManager"/> instance (to support chaining).</returns>
		private ConfigurationManager SetAttribute(string nodePath, string attribute, string value)
		{
			// validate params
			if (String.IsNullOrEmpty(nodePath)) { throw new ArgumentNullException("nodePath", "A node path is required in order to know what web.config setting should be changed."); }
			if (String.IsNullOrEmpty(attribute)) { throw new ArgumentNullException("attribute", "An attribute name is required in order to know what web.config setting should be changed."); }

			// add web config mod to app
			var mod = new SPWebConfigModification
			{
				Type = SPWebConfigModification.SPWebConfigModificationType.EnsureAttribute,
				Owner = this._owner,
				Path = nodePath,
				Name = attribute,
				Value = value,
				Sequence = 0
			};
			this._app.WebConfigModifications.Add(mod);

			// return config mgr to support chaining
			return this;
		}

		#endregion


		/// <summary>
		/// Generates a valid modification name based on the <paramref name="modificationXml"/>.
		/// </summary>
		/// <param name="modificationXml">The XML to be added within the modification.</param>
		/// <returns>A valid modification name based on the modification XML.</returns>
		private static string GenerateModificationName(string modificationXml)
		{
			var doc = new XmlDocument { InnerXml = modificationXml };
			var node = doc.ChildNodes[0];
			var nameBuilder = new StringBuilder();
			nameBuilder.Append(node.Name);
			for (var i = 0; i < node.Attributes.Count; i++)
			{
				nameBuilder.AppendFormat(@"[@{0}=""{1}""]", node.Attributes[i].Name, node.Attributes[i].Value);
			}
			return nameBuilder.ToString();
		}

		#endregion
	}
}